Spring的事件通知机制是一项很有用的功能,使用事件机制我们可以将相互耦合的代码解耦,从而方便功能的修改与添加。本文我来学习并分析一下Spring中事件的原理。
举个例子,假设有一个添加评论的方法,在评论添加成功之后需要进行修改redis缓存、给用户添加积分等等操作。当然可以在添加评论的代码后面假设这些操作,但是这样的代码违反了设计模式的多项原则:单一职责原则、迪米特法则、开闭原则。一句话说就是耦合性太大了,比如将来评论添加成功之后还需要有另外一个操作,这时候我们就需要去修改我们的添加评论代码了。
在以前的代码中,我使用观察者模式来解决这个问题。不过Spring中已经存在了一个升级版观察者模式的机制,这就是监听者模式。通过该机制我们就可以发送接收任意的事件并处理。
通过一个简单的demo来看看Spring事件通知的使用:
1 | // 定义一个事件 |
调用EventDemoPublish.publish
方法来发布消息,EventDemoListener
监听器接收到消息后对消息进行处理,打印出消息的内容:
1 | receiver hello |
Spring事件通知原理
首先我们跟踪publishEvent
方法,这个方法在AbstractApplicationContext
类中。
1 | protected void publishEvent(Object event, @Nullable ResolvableType eventType) { |
经过上面的分析,我们看到事件是通过applicationEventMulticaster
来广播出去的。
applicationEventMulticaster
在Spring的启动过程中被建立,我们在之前的文章Spring启动过程分析1(overview)中分析过Spring的启动过程,在核心方法refresh
中建立applicationEventMulticaster
:
1 | // Initialize message source for this context. |
关注initApplicationEventMulticaster
和registerListeners
方法。
1 | // 初始化事件广播器 |
1 | // 注册监听器 |
经过前面的分析,我们知道了事件广播器applicationEventMulticaster
如何被构建,下面我们分析事件的广播过程。
1 |
|
经过上面的分析,我们知道了Spring如何发送并响应事件。下面我们来分析如何使Spring能够异步响应事件。
异步响应Event
默认情况下,Spring是同步执行Event的响应方法的。如果响应方法的执行时间很长会阻塞发送事件的方法,因此很多场景下,我们需要让事件的响应异步化。
为了更直观地说明Event的响应默认是同步的,我们修改一下EventDemoListener
并增加一个EventDemoListener2
:
1 |
|
执行结果如下:
执行结果显示:EventDemoListener2
和EventDemoListener
的执行间隔1秒,EventDemoListener2
的执行和程序的结束也间隔1秒。结果表示我们的响应程序是同步执行的,一个响应程序的执行会阻塞下一个响应程序的执行。
自定义SimpleApplicationEventMulticaster
通过前面的代码分析,我们发现如果SimpleApplicationEventMulticaster
中的taskExecutor
如果不为null,将在taskExecutor
中异步执行响应程序。applicationEventMulticaster
的新建在initApplicationEventMulticaster
方法中,默认情况下它会新建一个SimpleApplicationEventMulticaster
,其中的taskExecutor
为null。因此想要taskExecutor
不为null,我们可以自己手动创建一个SimpleApplicationEventMulticaster
然后设置一个taskExecutor
。
修改Config
类:
1 |
|
此时再次执行程序,执行结果如下:
可以看到,EventDemoListener
和EventDemoListener2
是同时执行的,同时它们的执行没有阻塞主程序的执行。事件的响应做到了异步化。
@Async
前面我们看到,通过手动新建SimpleApplicationEventMulticaster
并设置TaskExecutor
可以使所有的事件响应程序都在另外的线程中执行,不阻塞主程序的执行。不过这样也带来一个问题,那就是所有的事件响应程序都异步化了,某些场景下我们希望某些关系密切的响应程序可以同步执行另外一些响应程序异步执行。这种场景下,我们就不能简单地新建SimpleApplicationEventMulticaster
并设置TaskExecutor
。
Spring中提供了一个@Async
注解,可以将加上这个注解的方法在另外的线程中执行。通过这个注解我们可以将指定的事件响应程序异步化。
我们修改EventDemoListener
,在onApplicationEvent
中加上@Async
注解;同时修改Config
类:
1 |
|
注意Config
类中需要加上@EnableAsync
注释,并定义TaskExecutor
。
执行结果如下:
我们看到,EventDemoListener
是在另外的线程中执行的,但是EventDemoListener2
仍然在主线程中执行,因此EventDemoListener2
阻塞了主线程的执行。
@Async原理
@Async
注解可以将方法异步化,下面我们来看看它的原理是什么。
我们在Config
类中添加了@EnableAsync
注释。@EnableAsync
注释引入AsyncConfigurationSelector
类,AsyncConfigurationSelector
类导入ProxyAsyncConfiguration
类,ProxyAsyncConfiguration
类新建过程中会新建AsyncAnnotationBeanPostProcessor
。
AsyncAnnotationBeanPostProcessor
类继承了BeanPostProcessor
,当每个Bean新建完成后会调用AsyncAnnotationBeanPostProcessor
的postProcessAfterInitialization
方法:
1 |
|
postProcessAfterInitialization
方法判断bean是否符合要求(方法上是否加了@Async
注释),如果符合要求则对bean加上代理,代理类为AnnotationAsyncExecutionInterceptor
。
1 |
|
调用我们的方法时首先调用AnnotationAsyncExecutionInterceptor
的invoke
方法,invoke
方法将我们真正的方法包装成一个Callable
任务,将这个任务提交到executor
中执行。由此达到了将我们的方法异步化的目的。
总结
Spring的事件机制是一套相当灵活的机制,使用它可以简便地将我们的代码解耦从而优化我们的代码。经过前面的分析我们了解了其中的运行原理,这有助于我们更好地使用这套机制。
https://www.jianshu.com/p/21984b08875c
https://juejin.im/post/5aee749bf265da0b71562ac1
https://my.oschina.net/u/2278977/blog/794868